Micron Document
🎖️GitЯра🎖️

Node / meshtastic / Meshtastic-Android / files / app / src / main / java / com / geeksville / mesh / service / SafeBluetooth.kt

Displaying Raw • Download

app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt ui_message_list (be3f882f) Text, 38.69 KB

T8b949e/*
* Copyright (c) 2025 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

Tff7b72package T7ee787com.geeksville.mesh.service

Tff7b72import T7ee787android.Manifest
Tff7b72import T7ee787android.annotation.SuppressLint
Tff7b72import T7ee787android.bluetooth.BluetoothDevice
Tff7b72import T7ee787android.bluetooth.BluetoothGatt
Tff7b72import T7ee787android.bluetooth.BluetoothGattCallback
Tff7b72import T7ee787android.bluetooth.BluetoothGattCharacteristic
Tff7b72import T7ee787android.bluetooth.BluetoothGattDescriptor
Tff7b72import T7ee787android.bluetooth.BluetoothManager
Tff7b72import T7ee787android.bluetooth.BluetoothProfile
Tff7b72import T7ee787android.content.Context
Tff7b72import T7ee787android.content.pm.PackageManager
Tff7b72import T7ee787android.os.Build
Tff7b72import T7ee787android.os.DeadObjectException
Tff7b72import T7ee787android.os.Handler
Tff7b72import T7ee787android.os.Looper
Tff7b72import T7ee787androidx.annotation.RequiresPermission
Tff7b72import T7ee787androidx.core.content.ContextCompat
Tff7b72import T7ee787com.geeksville.mesh.android.GeeksvilleApplication
Tff7b72import T7ee787com.geeksville.mesh.android.Logging
Tff7b72import T7ee787com.geeksville.mesh.concurrent.CallbackContinuation
Tff7b72import T7ee787com.geeksville.mesh.concurrent.Continuation
Tff7b72import T7ee787com.geeksville.mesh.concurrent.SyncContinuation
Tff7b72import T7ee787com.geeksville.mesh.util.exceptionReporter
Tff7b72import T7ee787kotlinx.coroutines.CoroutineScope
Tff7b72import T7ee787kotlinx.coroutines.Dispatchers
Tff7b72import T7ee787kotlinx.coroutines.Job
Tff7b72import T7ee787kotlinx.coroutines.Runnable
Tff7b72import T7ee787kotlinx.coroutines.delay
Tff7b72import T7ee787kotlinx.coroutines.launch
Tff7b72import T7ee787kotlinx.coroutines.runBlocking
Tff7b72import T7ee787kotlinx.coroutines.withTimeoutOrNull
Tff7b72import T7ee787java.io.Closeable
Tff7b72import T7ee787java.util.Random
Tff7b72import T7ee787java.util.UUID

T8b949e// / Return a standard BLE 128 bit UUID from the short 16 bit versions
Tff7b72fun Td2a8fflongBLEUUIDTb4b4b4(Te6edf3hexFourTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Te6edf3UUID Tff7b72= Te6edf3UUIDTb4b4b4.Te6edf3fromStringTb4b4b4(Ta5d6ff"Ta5d6ff0000Tffd700$Te6edf3hexFourTa5d6ff-0000-1000-8000-00805f9b34fbTa5d6ff"Tb4b4b4)

T8b949e/**
* Uses coroutines to safely access a bluetooth GATT device with a synchronous API
*
* The BTLE API on android is dumb. You can only have one outstanding operation in flight to the device. If you try to
* do something when something is pending, the operation just returns false. You are expected to chain your operations
* from the results callbacks.
*
* This class fixes the API by using coroutines to let you safely do a series of BTLE operations.
*/
Tff7b72class T56d364SafeBluetoothTb4b4b4(Tff7b72private Tff7b72val Te6edf3contextTb4b4b4: Te6edf3ContextTb4b4b4, Tff7b72private Tff7b72val Te6edf3deviceTb4b4b4: Te6edf3BluetoothDeviceTb4b4b4) Tb4b4b4:
Te6edf3LoggingTb4b4b4,
Te6edf3Closeable Tb4b4b4{

T8b949e// / Timeout before we declare a bluetooth operation failed (used for synchronous API operations only)
Tff7b72var Te6edf3timeoutMsec Tff7b72= T79c0ff2T79c0ff0 Tff7b72* T79c0ff1T79c0ff0T79c0ff0T79c0ff0L

T8b949e// / Users can access the GATT directly as needed
Tf0883e@Volatile Tff7b72var Te6edf3gattTb4b4b4: Te6edf3BluetoothGatt? Tff7b72= Tff7b72null

Tf0883e@Volatile Tff7b72var Te6edf3state Tff7b72= Te6edf3BluetoothProfileTb4b4b4.Te6edf3STATE_DISCONNECTED

Tf0883e@Volatile Tff7b72private Tff7b72var Te6edf3currentWorkTb4b4b4: Te6edf3BluetoothContinuation? Tff7b72= Tff7b72null
Tff7b72private Tff7b72val Te6edf3workQueue Tff7b72= Te6edf3mutableListOfTff7b72<Te6edf3BluetoothContinuationTff7b72>Tb4b4b4(Tb4b4b4)

T8b949e// Called for reconnection attemps
Tf0883e@Volatile Tff7b72private Tff7b72var Te6edf3connectionCallbackTb4b4b4: Tb4b4b4(Tb4b4b4(Te6edf3ResultTff7b72<Tffa657UnitTff7b72>Tb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4)Tff7b72? Tff7b72= Tff7b72null

Tf0883e@Volatile Tff7b72private Tff7b72var Te6edf3lostConnectCallbackTb4b4b4: Tb4b4b4(Tb4b4b4(Tb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4)Tff7b72? Tff7b72= Tff7b72null

T8b949e// / from characteristic UUIDs to the handler function for notfies
Tff7b72private Tff7b72val Te6edf3notifyHandlers Tff7b72= Te6edf3mutableMapOfTff7b72<Te6edf3UUIDTb4b4b4, Tb4b4b4(Te6edf3BluetoothGattCharacteristicTb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTff7b72>Tb4b4b4(Tb4b4b4)

Tff7b72private Tff7b72val Te6edf3serviceScope Tff7b72= Te6edf3CoroutineScopeTb4b4b4(Te6edf3DispatchersTb4b4b4.Te6edf3IOTb4b4b4)

T8b949e/** A BLE status code based error */
Tff7b72class T56d364BLEStatusExceptionTb4b4b4(Tff7b72val Te6edf3statusTb4b4b4: Tffa657IntTb4b4b4, Te6edf3msgTb4b4b4: Tffa657StringTb4b4b4) Tb4b4b4: Te6edf3BLEExceptionTb4b4b4(Te6edf3msgTb4b4b4)

T8b949e// 0x2902 org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
Tff7b72private Tff7b72val Te6edf3configurationDescriptorUUID Tff7b72= Te6edf3longBLEUUIDTb4b4b4(Ta5d6ff"Ta5d6ff2902Ta5d6ff"Tb4b4b4)

T8b949e/**
* a schedulable bit of bluetooth work, includes both the closure to call to start the operation and the completion
* (either async or sync) to call when it completes
*/
Tff7b72private Tff7b72class T56d364BluetoothContinuationTb4b4b4(
Tff7b72val Te6edf3tagTb4b4b4: Tffa657StringTb4b4b4,
Tff7b72val Te6edf3completionTb4b4b4: Te6edf3comTb4b4b4.Te6edf3geeksvilleTb4b4b4.Te6edf3meshTb4b4b4.Te6edf3concurrentTb4b4b4.Te6edf3ContinuationTff7b72<Tff7b72*Tff7b72>Tb4b4b4,
Tff7b72val Te6edf3timeoutMillisTb4b4b4: Tffa657Long Tff7b72= T79c0ff0Tb4b4b4, T8b949e// If we want to timeout this operation at a certain time, use a non zero value
Tff7b72private Tff7b72val Te6edf3startWorkFnTb4b4b4: Tb4b4b4(Tb4b4b4) Tff7b72-Tff7b72> Tffa657BooleanTb4b4b4,
Tb4b4b4) Tb4b4b4: Te6edf3Logging Tb4b4b4{

T8b949e// / Start running a queued bit of work, return true for success or false for fatal bluetooth error
Tff7b72fun Td2a8ffstartWorkTb4b4b4(Tb4b4b4)Tb4b4b4: Tffa657Boolean Tb4b4b4{
Te6edf3debugTb4b4b4(Ta5d6ff"Ta5d6ffStarting work: Tffd700$Te6edf3tagTa5d6ff"Tb4b4b4)
Tff7b72return Te6edf3startWorkFnTb4b4b4(Tb4b4b4)
Tb4b4b4}

Tff7b72override Tff7b72fun Td2a8fftoStringTb4b4b4(Tb4b4b4)Tb4b4b4: Tffa657String Tff7b72= Ta5d6ff"Ta5d6ffWork:Tffd700$Te6edf3tagTa5d6ff"

T8b949e// / Connection work items are treated specially
Tff7b72fun Td2a8ffisConnectTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3tag Tff7b72=Tff7b72= Ta5d6ff"Ta5d6ffconnectTa5d6ff" Tff7b72|Tff7b72| Te6edf3tag Tff7b72=Tff7b72= Ta5d6ff"Ta5d6ffreconnectTa5d6ff"
Tb4b4b4}

T8b949e/**
* skanky hack to restart BLE if it says it is hosed
* https://stackoverflow.com/questions/35103701/ble-android-onconnectionstatechange-not-being-called
*/
Tff7b72private Tff7b72val Te6edf3mHandlerTb4b4b4: Te6edf3Handler Tff7b72= Te6edf3HandlerTb4b4b4(Te6edf3LooperTb4b4b4.Te6edf3getMainLooperTb4b4b4(Tb4b4b4)Tb4b4b4)

T8b949e/**
* Attempts an emergency restart of the Bluetooth adapter. This is a workaround for certain BLE stack issues. It
* checks for necessary permissions (BLUETOOTH_CONNECT on API 31+, BLUETOOTH_ADMIN on older versions) before
* attempting to disable and then re-enable the adapter.
*/
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffReturnCountTa5d6ff"Tb4b4b4)
Tff7b72fun Td2a8ffrestartBleTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3GeeksvilleApplicationTb4b4b4.Te6edf3analyticsTb4b4b4.Te6edf3trackTb4b4b4(Ta5d6ff"Ta5d6ffble_restartTa5d6ff"Tb4b4b4) T8b949e// record # of times we needed to use this nasty hack
Te6edf3errormsgTb4b4b4(Ta5d6ff"Ta5d6ffDoing emergency BLE restartTa5d6ff"Tb4b4b4)

Tff7b72val Te6edf3bluetoothManager Tff7b72= Te6edf3contextTb4b4b4.Te6edf3getSystemServiceTb4b4b4(Te6edf3ContextTb4b4b4.Te6edf3BLUETOOTH_SERVICETb4b4b4) Tff7b72as? Te6edf3BluetoothManager
Tff7b72val Te6edf3adapter Tff7b72= Te6edf3bluetoothManagerTff7b72?.Te6edf3adapter

Tff7b72if Tb4b4b4(Te6edf3adapter Tff7b72=Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3errormsgTb4b4b4(Ta5d6ff"Ta5d6ffBluetoothAdapter not available for BLE restart.Ta5d6ff"Tb4b4b4)
Tff7b72return
Tb4b4b4}

Tff7b72val Te6edf3hasPermission Tff7b72=
Tff7b72if Tb4b4b4(Te6edf3BuildTb4b4b4.Te6edf3VERSIONTb4b4b4.Te6edf3SDK_INT Tff7b72>Tff7b72= Te6edf3BuildTb4b4b4.Te6edf3VERSION_CODESTb4b4b4.Te6edf3STb4b4b4) Tb4b4b4{
Te6edf3ContextCompatTb4b4b4.Te6edf3checkSelfPermissionTb4b4b4(Te6edf3contextTb4b4b4, Te6edf3ManifestTb4b4b4.Te6edf3permissionTb4b4b4.Te6edf3BLUETOOTH_CONNECTTb4b4b4) Tff7b72=Tff7b72=
Te6edf3PackageManagerTb4b4b4.Te6edf3PERMISSION_GRANTED
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3ContextCompatTb4b4b4.Te6edf3checkSelfPermissionTb4b4b4(Te6edf3contextTb4b4b4, Te6edf3ManifestTb4b4b4.Te6edf3permissionTb4b4b4.Te6edf3BLUETOOTH_ADMINTb4b4b4) Tff7b72=Tff7b72=
Te6edf3PackageManagerTb4b4b4.Te6edf3PERMISSION_GRANTED
Tb4b4b4}

Tff7b72if Tb4b4b4(Tff7b72!Te6edf3hasPermissionTb4b4b4) Tb4b4b4{
Te6edf3errormsgTb4b4b4(Ta5d6ff"Ta5d6ffMissing Bluetooth permission (CONNECT or ADMIN) for BLE restart.Ta5d6ff"Tb4b4b4)
Tff7b72return
Tb4b4b4}

Tff7b72if Tb4b4b4(Te6edf3adapterTb4b4b4.Te6edf3isEnabledTb4b4b4) Tb4b4b4{
Te6edf3warnTb4b4b4(Ta5d6ff"Ta5d6ffAttempting to disable Bluetooth adapter.Ta5d6ff"Tb4b4b4)
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3adapterTb4b4b4.Te6edf3disableTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3errormsgTb4b4b4(Ta5d6ff"Ta5d6ffadapter.disable() failed.Ta5d6ff"Tb4b4b4)
Tff7b72return
Tb4b4b4}
T8b949e// TODO: display some kind of UI about restarting BLE
Te6edf3mHandlerTb4b4b4.Te6edf3postDelayedTb4b4b4(
Tff7b72object Tb4b4b4: T56d364Runnable Tb4b4b4{
Tf0883e@RequiresPermissionTb4b4b4(Te6edf3ManifestTb4b4b4.Te6edf3permissionTb4b4b4.Te6edf3BLUETOOTH_CONNECTTb4b4b4)
Tff7b72override Tff7b72fun Td2a8ffrunTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3adapterTb4b4b4.Te6edf3isEnabledTb4b4b4) Tb4b4b4{
Te6edf3warnTb4b4b4(Ta5d6ff"Ta5d6ffAttempting to re-enable Bluetooth adapter.Ta5d6ff"Tb4b4b4)
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3adapterTb4b4b4.Te6edf3enableTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3errormsgTb4b4b4(Ta5d6ff"Ta5d6ffadapter.enable() failed.Ta5d6ff"Tb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3infoTb4b4b4(Ta5d6ff"Ta5d6ffBluetooth adapter re-enabled.Ta5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4} Tff7b72else Tb4b4b4{
T8b949e// Adapter might have been re-enabled by user or another process, or disable() is async and
T8b949e// hasn't completed.
T8b949e// Or, isEnabled check post-disable was too quick.
T8b949e// If it's still enabled, we retry enabling check later, assuming disable will eventually
T8b949e// take effect.
Te6edf3warnTb4b4b4(Ta5d6ff"Ta5d6ffAdapter still enabled, retrying enable check soon.Ta5d6ff"Tb4b4b4)
Te6edf3mHandlerTb4b4b4.Te6edf3postDelayedTb4b4b4(Tff7b72thisTb4b4b4, T79c0ff2T79c0ff5T79c0ff0T79c0ff0Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}Tb4b4b4,
T79c0ff2T79c0ff5T79c0ff0T79c0ff0Tb4b4b4,
Tb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3infoTb4b4b4(Ta5d6ff"Ta5d6ffBluetooth adapter already disabled, attempting to enable.Ta5d6ff"Tb4b4b4)
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3adapterTb4b4b4.Te6edf3enableTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3errormsgTb4b4b4(Ta5d6ff"Ta5d6ffadapter.enable() failed while adapter was already disabled.Ta5d6ff"Tb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3infoTb4b4b4(Ta5d6ff"Ta5d6ffBluetooth adapter enabled.Ta5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

Tff7b72companion Tff7b72object Tb4b4b4{
Tff7b72private Tff7b72const Tff7b72val Te6edf3STATUS_RELIABLE_WRITE_FAILED Tff7b72= T79c0ff4T79c0ff4T79c0ff0T79c0ff3
Tff7b72private Tff7b72const Tff7b72val Te6edf3STATUS_TIMEOUT Tff7b72= T79c0ff4T79c0ff4T79c0ff0T79c0ff4
Tff7b72private Tff7b72const Tff7b72val Te6edf3STATUS_NOSTART Tff7b72= T79c0ff4T79c0ff4T79c0ff0T79c0ff5
Tff7b72private Tff7b72const Tff7b72val Te6edf3STATUS_SIMFAILURE Tff7b72= T79c0ff4T79c0ff4T79c0ff0T79c0ff6
Tb4b4b4}

T8b949e// Our own custom BLE status codes

T8b949e/**
* Should we automatically try to reconnect when we lose our connection?
*
* Originally this was true, but over time (now that clients are smarter and need to build up more state) I see this
* was a mistake. Now if the connection drops we just call the lostConnection callback and the client of this API is
* responsible for reconnecting. This also prevents nasty races when sometimes both the upperlayer and this layer
* decide to reconnect simultaneously.
*/
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffUnusedPrivatePropertyTa5d6ff"Tb4b4b4)
Tff7b72private Tff7b72val Te6edf3autoReconnect Tff7b72= Tff7b72false

Tff7b72private Tff7b72val Te6edf3gattCallback Tff7b72=
Tff7b72object Tb4b4b4: T56d364BluetoothGattCallbackTb4b4b4(Tb4b4b4) Tb4b4b4{

Tf0883e@SuppressLintTb4b4b4(Ta5d6ff"Ta5d6ffMissingPermissionTa5d6ff"Tb4b4b4)
Tff7b72override Tff7b72fun Td2a8ffonConnectionStateChangeTb4b4b4(Te6edf3gTb4b4b4: Te6edf3BluetoothGattTb4b4b4, Te6edf3statusTb4b4b4: Tffa657IntTb4b4b4, Te6edf3newStateTb4b4b4: Tffa657IntTb4b4b4) Tff7b72= Te6edf3exceptionReporter Tb4b4b4{
Te6edf3infoTb4b4b4(Ta5d6ff"Ta5d6ffnew bluetooth connection state Tffd700$Te6edf3newStateTa5d6ff, status Tffd700$Te6edf3statusTa5d6ff"Tb4b4b4)

Tff7b72when Tb4b4b4(Te6edf3newStateTb4b4b4) Tb4b4b4{
Te6edf3BluetoothProfileTb4b4b4.Te6edf3STATE_CONNECTED Tff7b72-Tff7b72> Tb4b4b4{
Te6edf3state Tff7b72= Te6edf3newState T8b949e// we only care about connected/disconnected - not the transitional states

T8b949e// If autoconnect is on and this connect attempt failed, hopefully some future attempt will
T8b949e// succeed
Tff7b72if Tb4b4b4(Te6edf3status Tff7b72!Tff7b72= Te6edf3BluetoothGattTb4b4b4.Te6edf3GATT_SUCCESS Tff7b72&Tff7b72& Te6edf3autoConnectTb4b4b4) Tb4b4b4{
Te6edf3errormsgTb4b4b4(Ta5d6ff"Ta5d6ffConnect attempt failed Tffd700$Te6edf3statusTa5d6ff, not calling connect completion handler...Ta5d6ff"Tb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3completeWorkTb4b4b4(Te6edf3statusTb4b4b4, Tffa657UnitTb4b4b4)
Tb4b4b4}
Tb4b4b4}

Te6edf3BluetoothProfileTb4b4b4.Te6edf3STATE_DISCONNECTED Tff7b72-Tff7b72> Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3gatt Tff7b72=Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3errormsgTb4b4b4(Ta5d6ff"Ta5d6ffNo gatt: ignoring connection state Tffd700$Te6edf3newStateTa5d6ff, status Tffd700$Te6edf3statusTa5d6ff"Tb4b4b4)
Tb4b4b4} Tff7b72else Tff7b72if Tb4b4b4(Te6edf3isClosingTb4b4b4) Tb4b4b4{
Te6edf3infoTb4b4b4(Ta5d6ff"Ta5d6ffGot disconnect because we are shutting down, closing gattTa5d6ff"Tb4b4b4)
Te6edf3gatt Tff7b72= Tff7b72null
Te6edf3gTb4b4b4.Te6edf3closeTb4b4b4(Tb4b4b4) T8b949e// Finish closing our gatt here
Tb4b4b4} Tff7b72else Tb4b4b4{
T8b949e// cancel any queued ops if we were already connected
Tff7b72val Te6edf3oldstate Tff7b72= Te6edf3state
Te6edf3state Tff7b72= Te6edf3newState
Tff7b72if Tb4b4b4(Te6edf3oldstate Tff7b72=Tff7b72= Te6edf3BluetoothProfileTb4b4b4.Te6edf3STATE_CONNECTEDTb4b4b4) Tb4b4b4{
Te6edf3infoTb4b4b4(Ta5d6ff"Ta5d6ffLost connection - aborting current work: Tffd700$Te6edf3currentWorkTa5d6ff"Tb4b4b4)

T8b949e// If we get a disconnect, just try again otherwise fail all current operations
T8b949e// Note: if no work is pending (likely) we also just totally teardown and restart
T8b949e// the connection, because we won't be
T8b949e// throwing a lost connection exception to any worker.
Tff7b72if Tb4b4b4(Te6edf3autoConnect Tff7b72&Tff7b72& Tb4b4b4(Te6edf3currentWork Tff7b72=Tff7b72= Tff7b72null Tff7b72|Tff7b72| Te6edf3currentWorkTff7b72?.Te6edf3isConnectTb4b4b4(Tb4b4b4) Tff7b72=Tff7b72= Tff7b72trueTb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3dropAndReconnectTb4b4b4(Tb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3lostConnectionTb4b4b4(Ta5d6ff"Ta5d6fflost connectionTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4} Tff7b72else Tff7b72if Tb4b4b4(Te6edf3status Tff7b72=Tff7b72= T79c0ff1T79c0ff3T79c0ff3Tb4b4b4) Tb4b4b4{
T8b949e// We were not previously connected and we just failed with our non-auto connection
T8b949e// attempt. Therefore we now need
T8b949e// to do an autoconnection attempt. When that attempt succeeds/fails the normal
T8b949e// callbacks will be called

T8b949e// Note: To workaround https://issuetracker.google.com/issues/36995652
T8b949e// Always call BluetoothDevice#connectGatt() with autoConnect=false
T8b949e// (the race condition does not affect that case). If that connection times out
T8b949e// you will get a callback with status=133. Then call BluetoothGatt#connect()
T8b949e// to initiate a background connection.
Tff7b72if Tb4b4b4(Te6edf3autoConnectTb4b4b4) Tb4b4b4{
Te6edf3warnTb4b4b4(Ta5d6ff"Ta5d6ffFailed on non-auto connect, falling back to auto connect attemptTa5d6ff"Tb4b4b4)
Te6edf3closeGattTb4b4b4(Tb4b4b4) T8b949e// Close the old non-auto connection
Te6edf3lowLevelConnectTb4b4b4(Tff7b72trueTb4b4b4)
Tb4b4b4}
Tb4b4b4} Tff7b72else Tff7b72if Tb4b4b4(Te6edf3status Tff7b72=Tff7b72= T79c0ff1T79c0ff4T79c0ff7Tb4b4b4) Tb4b4b4{
Te6edf3infoTb4b4b4(Ta5d6ff"Ta5d6ffgot 147, calling lostConnection()Ta5d6ff"Tb4b4b4)
Te6edf3lostConnectionTb4b4b4(Ta5d6ff"Ta5d6ffcode 147Ta5d6ff"Tb4b4b4)
Tb4b4b4}

Tff7b72if Tb4b4b4(Te6edf3status Tff7b72=Tff7b72= T79c0ff2T79c0ff5T79c0ff7Tb4b4b4) Tb4b4b4{ T8b949e// mystery error code when phone is hung
T8b949e// throw Exception("Mystery bluetooth failure - debug me")
Te6edf3restartBleTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

Tff7b72override Tff7b72fun Td2a8ffonServicesDiscoveredTb4b4b4(Te6edf3gattTb4b4b4: Te6edf3BluetoothGattTb4b4b4, Te6edf3statusTb4b4b4: Tffa657IntTb4b4b4) Tb4b4b4{
T8b949e// For testing lie and claim failure
Te6edf3completeWorkTb4b4b4(Te6edf3statusTb4b4b4, Tffa657UnitTb4b4b4)
Tb4b4b4}

Tff7b72override Tff7b72fun Td2a8ffonCharacteristicReadTb4b4b4(
Te6edf3gattTb4b4b4: Te6edf3BluetoothGattTb4b4b4,
Te6edf3characteristicTb4b4b4: Te6edf3BluetoothGattCharacteristicTb4b4b4,
Te6edf3statusTb4b4b4: Tffa657IntTb4b4b4,
Tb4b4b4) Tb4b4b4{
Te6edf3completeWorkTb4b4b4(Te6edf3statusTb4b4b4, Te6edf3characteristicTb4b4b4)
Tb4b4b4}

Tff7b72override Tff7b72fun Td2a8ffonReliableWriteCompletedTb4b4b4(Te6edf3gattTb4b4b4: Te6edf3BluetoothGattTb4b4b4, Te6edf3statusTb4b4b4: Tffa657IntTb4b4b4) Tb4b4b4{
Te6edf3completeWorkTb4b4b4(Te6edf3statusTb4b4b4, Tffa657UnitTb4b4b4)
Tb4b4b4}

Tf0883e@RequiresPermissionTb4b4b4(Te6edf3ManifestTb4b4b4.Te6edf3permissionTb4b4b4.Te6edf3BLUETOOTH_CONNECTTb4b4b4)
Tff7b72override Tff7b72fun Td2a8ffonCharacteristicWriteTb4b4b4(
Te6edf3gattTb4b4b4: Te6edf3BluetoothGattTb4b4b4,
Te6edf3characteristicTb4b4b4: Te6edf3BluetoothGattCharacteristicTb4b4b4,
Te6edf3statusTb4b4b4: Tffa657IntTb4b4b4,
Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3reliable Tff7b72= Te6edf3currentReliableWrite
Tff7b72if Tb4b4b4(Te6edf3reliable Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3characteristicTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3contentEqualsTb4b4b4(Te6edf3reliableTb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3errormsgTb4b4b4(Ta5d6ff"Ta5d6ffA reliable write failed!Ta5d6ff"Tb4b4b4)
Te6edf3gattTb4b4b4.Te6edf3abortReliableWriteTb4b4b4(Tb4b4b4)
Te6edf3completeWorkTb4b4b4(Te6edf3STATUS_RELIABLE_WRITE_FAILEDTb4b4b4, Te6edf3characteristicTb4b4b4) T8b949e// skanky code to indicate failure
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3logAssertTb4b4b4(Te6edf3gattTb4b4b4.Te6edf3executeReliableWriteTb4b4b4(Tb4b4b4)Tb4b4b4)
T8b949e// After this execute reliable completes - we can continue with normal operations (see
T8b949e// onReliableWriteCompleted)
Tb4b4b4}
Tb4b4b4} Tff7b72else Tb4b4b4{ T8b949e// Just a standard write - do the normal flow
Te6edf3completeWorkTb4b4b4(Te6edf3statusTb4b4b4, Te6edf3characteristicTb4b4b4)
Tb4b4b4}
Tb4b4b4}

Tff7b72override Tff7b72fun Td2a8ffonMtuChangedTb4b4b4(Te6edf3gattTb4b4b4: Te6edf3BluetoothGattTb4b4b4, Te6edf3mtuTb4b4b4: Tffa657IntTb4b4b4, Te6edf3statusTb4b4b4: Tffa657IntTb4b4b4) Tb4b4b4{
T8b949e// Alas, passing back an Int mtu isn't working and since I don't really care what MTU
T8b949e// the device was willing to let us have I'm just punting and returning Unit
Tff7b72if Tb4b4b4(Te6edf3isSettingMtuTb4b4b4) Tb4b4b4{
Te6edf3completeWorkTb4b4b4(Te6edf3statusTb4b4b4, Tffa657UnitTb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3errormsgTb4b4b4(Ta5d6ff"Ta5d6ffIgnoring bogus onMtuChangedTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4}

T8b949e/**
* Callback triggered as a result of a remote characteristic notification.
*
* @param gatt GATT client the characteristic is associated with
* @param characteristic Characteristic that has been updated as a result of a remote notification event.
*/
Tff7b72override Tff7b72fun Td2a8ffonCharacteristicChangedTb4b4b4(Te6edf3gattTb4b4b4: Te6edf3BluetoothGattTb4b4b4, Te6edf3characteristicTb4b4b4: Te6edf3BluetoothGattCharacteristicTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3handler Tff7b72= Te6edf3notifyHandlersTb4b4b4.Te6edf3getTb4b4b4(Te6edf3characteristicTb4b4b4.Te6edf3uuidTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3handler Tff7b72=Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3warnTb4b4b4(Ta5d6ff"Ta5d6ffReceived notification from Tffd700$Te6edf3characteristicTa5d6ff, but no handler registeredTa5d6ff"Tb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3exceptionReporter Tb4b4b4{ Te6edf3handlerTb4b4b4(Te6edf3characteristicTb4b4b4) Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

T8b949e/**
* Callback indicating the result of a descriptor write operation.
*
* @param gatt GATT client invoked [BluetoothGatt.writeDescriptor]
* @param descriptor Descriptor that was writte to the associated remote device.
* @param status The result of the write operation [BluetoothGatt.GATT_SUCCESS] if the operation succeeds.
*/
Tff7b72override Tff7b72fun Td2a8ffonDescriptorWriteTb4b4b4(Te6edf3gattTb4b4b4: Te6edf3BluetoothGattTb4b4b4, Te6edf3descriptorTb4b4b4: Te6edf3BluetoothGattDescriptorTb4b4b4, Te6edf3statusTb4b4b4: Tffa657IntTb4b4b4) Tb4b4b4{
Te6edf3completeWorkTb4b4b4(Te6edf3statusTb4b4b4, Te6edf3descriptorTb4b4b4)
Tb4b4b4}

T8b949e/**
* Callback reporting the result of a descriptor read operation.
*
* @param gatt GATT client invoked [BluetoothGatt.readDescriptor]
* @param descriptor Descriptor that was read from the associated remote device.
* @param status [BluetoothGatt.GATT_SUCCESS] if the read operation was completed successfully
*/
Tff7b72override Tff7b72fun Td2a8ffonDescriptorReadTb4b4b4(Te6edf3gattTb4b4b4: Te6edf3BluetoothGattTb4b4b4, Te6edf3descriptorTb4b4b4: Te6edf3BluetoothGattDescriptorTb4b4b4, Te6edf3statusTb4b4b4: Tffa657IntTb4b4b4) Tb4b4b4{
Te6edf3completeWorkTb4b4b4(Te6edf3statusTb4b4b4, Te6edf3descriptorTb4b4b4)
Tb4b4b4}
Tb4b4b4}

T8b949e// To test loss of BLE faults we can randomly fail a certain % of all work items. We
T8b949e// skip this for "connect" items because the handling for connection failure is special
Tff7b72var Te6edf3simFailures Tff7b72= Tff7b72false
Tff7b72var Te6edf3failPercent Tff7b72=
T79c0ff1T79c0ff0 T8b949e// 15% failure is unusably high because of constant reconnects, 7% somewhat usable, 10% pretty bad
Tff7b72private Tff7b72val Te6edf3failRandom Tff7b72= Te6edf3RandomTb4b4b4(Tb4b4b4)

Tff7b72private Tff7b72var Te6edf3activeTimeoutTb4b4b4: Te6edf3Job? Tff7b72= Tff7b72null

T8b949e// / If we have work we can do, start doing it.
Tff7b72private Tff7b72fun Td2a8ffstartNewWorkTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3logAssertTb4b4b4(Te6edf3currentWork Tff7b72=Tff7b72= Tff7b72nullTb4b4b4)

Tff7b72if Tb4b4b4(Te6edf3workQueueTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3newWork Tff7b72= Te6edf3workQueueTb4b4b4.Te6edf3removeAtTb4b4b4(T79c0ff0Tb4b4b4)
Te6edf3currentWork Tff7b72= Te6edf3newWork

Tff7b72if Tb4b4b4(Te6edf3newWorkTb4b4b4.Te6edf3timeoutMillis Tff7b72!Tff7b72= T79c0ff0LTb4b4b4) Tb4b4b4{
Te6edf3activeTimeout Tff7b72=
Te6edf3serviceScopeTb4b4b4.Te6edf3launch Tb4b4b4{
T8b949e// debug("Starting failsafe timer ${newWork.timeoutMillis}")
Te6edf3delayTb4b4b4(Te6edf3newWorkTb4b4b4.Te6edf3timeoutMillisTb4b4b4)
Te6edf3errormsgTb4b4b4(Ta5d6ff"Ta5d6ffFailsafe BLE timer expired!Ta5d6ff"Tb4b4b4)
Te6edf3completeWorkTb4b4b4(Te6edf3STATUS_TIMEOUTTb4b4b4, Tffa657UnitTb4b4b4) T8b949e// Throw an exception in that work
Tb4b4b4}
Tb4b4b4}

Te6edf3isSettingMtu Tff7b72= Tff7b72false T8b949e// Most work is not doing MTU stuff, the work that is will re set this flag

Tff7b72val Te6edf3failThis Tff7b72= Te6edf3simFailures Tff7b72&Tff7b72& Tff7b72!Te6edf3newWorkTb4b4b4.Te6edf3isConnectTb4b4b4(Tb4b4b4) Tff7b72&Tff7b72& Te6edf3failRandomTb4b4b4.Te6edf3nextIntTb4b4b4(T79c0ff1T79c0ff0T79c0ff0Tb4b4b4) Tff7b72< Te6edf3failPercent

Tff7b72if Tb4b4b4(Te6edf3failThisTb4b4b4) Tb4b4b4{
Te6edf3errormsgTb4b4b4(Ta5d6ff"Ta5d6ffSimulating random work failure!Ta5d6ff"Tb4b4b4)
Te6edf3completeWorkTb4b4b4(Te6edf3STATUS_SIMFAILURETb4b4b4, Tffa657UnitTb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Tff7b72val Te6edf3started Tff7b72= Te6edf3newWorkTb4b4b4.Te6edf3startWorkTb4b4b4(Tb4b4b4)
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3startedTb4b4b4) Tb4b4b4{
Te6edf3errormsgTb4b4b4(Ta5d6ff"Ta5d6ffFailed to start work, returned error statusTa5d6ff"Tb4b4b4)
Te6edf3completeWorkTb4b4b4(Te6edf3STATUS_NOSTARTTb4b4b4, Tffa657UnitTb4b4b4) T8b949e// abandon the current attempt and try for another
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

Tff7b72private Tff7b72fun Tff7b72<Te6edf3TTff7b72> Td2a8ffqueueWorkTb4b4b4(Te6edf3tagTb4b4b4: Tffa657StringTb4b4b4, Te6edf3contTb4b4b4: Te6edf3ContinuationTff7b72<Te6edf3TTff7b72>Tb4b4b4, Te6edf3timeoutTb4b4b4: Tffa657LongTb4b4b4, Te6edf3initFnTb4b4b4: Tb4b4b4(Tb4b4b4) Tff7b72-Tff7b72> Tffa657BooleanTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3btCont Tff7b72= Te6edf3BluetoothContinuationTb4b4b4(Te6edf3tagTb4b4b4, Te6edf3contTb4b4b4, Te6edf3timeoutTb4b4b4, Te6edf3initFnTb4b4b4)

Te6edf3synchronizedTb4b4b4(Te6edf3workQueueTb4b4b4) Tb4b4b4{
Te6edf3debugTb4b4b4(Ta5d6ff"Ta5d6ffEnqueuing work: Tffd700${Te6edf3btContTb4b4b4.Te6edf3tagTffd700}Ta5d6ff"Tb4b4b4)
Te6edf3workQueueTb4b4b4.Te6edf3addTb4b4b4(Te6edf3btContTb4b4b4)

T8b949e// if we don't have any outstanding operations, run first item in queue
Tff7b72if Tb4b4b4(Te6edf3currentWork Tff7b72=Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3startNewWorkTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

T8b949e/** Stop any current work */
Tff7b72private Tff7b72fun Td2a8ffstopCurrentWorkTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3activeTimeoutTff7b72?.Te6edf3let Tb4b4b4{
Tffa657itTb4b4b4.Te6edf3cancelTb4b4b4(Tb4b4b4)
Te6edf3activeTimeout Tff7b72= Tff7b72null
Tb4b4b4}
Te6edf3currentWork Tff7b72= Tff7b72null
Tb4b4b4}

T8b949e/** Called from our big GATT callback, completes the current job and then schedules a new one */
Tff7b72private Tff7b72fun Tff7b72<Te6edf3T Tb4b4b4: Te6edf3AnyTff7b72> Td2a8ffcompleteWorkTb4b4b4(Te6edf3statusTb4b4b4: Tffa657IntTb4b4b4, Te6edf3resTb4b4b4: Te6edf3TTb4b4b4) Tb4b4b4{
Te6edf3exceptionReporter Tb4b4b4{
T8b949e// We might unexpectedly fail inside here, but we don't want to pass that exception back up to the bluetooth
T8b949e// GATT layer

T8b949e// startup next job in queue before calling the completion handler
Tff7b72val Te6edf3work Tff7b72=
Te6edf3synchronizedTb4b4b4(Te6edf3workQueueTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3w Tff7b72= Te6edf3currentWork

Tff7b72if Tb4b4b4(Te6edf3w Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3stopCurrentWorkTb4b4b4(Tb4b4b4) T8b949e// We are now no longer working on anything

Te6edf3startNewWorkTb4b4b4(Tb4b4b4)
Tb4b4b4}
Te6edf3w
Tb4b4b4}

Tff7b72if Tb4b4b4(Te6edf3work Tff7b72=Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3warnTb4b4b4(Ta5d6ff"Ta5d6ffwor completed, but we already killed it via failsafetimer? status=Tffd700$Te6edf3statusTa5d6ff, res=Tffd700$Te6edf3resTa5d6ff"Tb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
T8b949e// debug("work ${work.tag} is completed, resuming status=$status, res=$res")
Tff7b72if Tb4b4b4(Te6edf3status Tff7b72!Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Te6edf3workTb4b4b4.Te6edf3completionTb4b4b4.Te6edf3resumeWithExceptionTb4b4b4(
Te6edf3BLEStatusExceptionTb4b4b4(Te6edf3statusTb4b4b4, Ta5d6ff"Ta5d6ffBluetooth status=Tffd700$Te6edf3statusTa5d6ff while doing Tffd700${Te6edf3workTb4b4b4.Te6edf3tagTffd700}Ta5d6ff"Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3workTb4b4b4.Te6edf3completionTb4b4b4.Te6edf3resumeTb4b4b4(Te6edf3ResultTb4b4b4.Te6edf3successTb4b4b4(Te6edf3resTb4b4b4) Tff7b72as Te6edf3ResultTff7b72<Te6edf3NothingTff7b72>Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

T8b949e/** Something went wrong, abort all queued */
Tff7b72private Tff7b72fun Td2a8fffailAllWorkTb4b4b4(Te6edf3exTb4b4b4: Te6edf3ExceptionTb4b4b4) Tb4b4b4{
Te6edf3synchronizedTb4b4b4(Te6edf3workQueueTb4b4b4) Tb4b4b4{
Te6edf3warnTb4b4b4(Ta5d6ff"Ta5d6ffFailing Tffd700${Te6edf3workQueueTb4b4b4.Te6edf3sizeTffd700}Ta5d6ff works, because Tffd700${Te6edf3exTb4b4b4.Te6edf3messageTffd700}Ta5d6ff"Tb4b4b4)
Te6edf3workQueueTb4b4b4.Te6edf3forEach Tb4b4b4{
Tff7b72try Tb4b4b4{
Tffa657itTb4b4b4.Te6edf3completionTb4b4b4.Te6edf3resumeWithExceptionTb4b4b4(Te6edf3exTb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3exTb4b4b4: Te6edf3ExceptionTb4b4b4) Tb4b4b4{
Te6edf3errormsgTb4b4b4(Ta5d6ff"Ta5d6ffMystery exception, why were we informed about our own exceptions?Ta5d6ff"Tb4b4b4, Te6edf3exTb4b4b4)
Tb4b4b4}
Tb4b4b4}
Te6edf3workQueueTb4b4b4.Te6edf3clearTb4b4b4(Tb4b4b4)
Te6edf3stopCurrentWorkTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4}

T8b949e// / helper glue to make sync continuations and then wait for the result
Tff7b72private Tff7b72fun Tff7b72<Te6edf3TTff7b72> Td2a8ffmakeSyncTb4b4b4(Te6edf3wrappedFnTb4b4b4: Tb4b4b4(Te6edf3SyncContinuationTff7b72<Te6edf3TTff7b72>Tb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4)Tb4b4b4: Te6edf3T Tb4b4b4{
Tff7b72val Te6edf3cont Tff7b72= Te6edf3SyncContinuationTff7b72<Te6edf3TTff7b72>Tb4b4b4(Tb4b4b4)
Te6edf3wrappedFnTb4b4b4(Te6edf3contTb4b4b4)
Tff7b72return Te6edf3contTb4b4b4.Te6edf3awaitTb4b4b4(Tb4b4b4) T8b949e// was timeoutMsec but we now do the timeout at the lower BLE level
Tb4b4b4}

T8b949e// Is the gatt trying to repeatedly connect as needed?
Tff7b72private Tff7b72var Te6edf3autoConnect Tff7b72= Tff7b72false

T8b949e// / True if the current active connection is auto (possible for this to be false but autoConnect to be true
T8b949e// / if we are in the first non-automated lowLevel connect.
Tff7b72private Tff7b72var Te6edf3currentConnectIsAuto Tff7b72= Tff7b72false

Tf0883e@RequiresPermissionTb4b4b4(Te6edf3ManifestTb4b4b4.Te6edf3permissionTb4b4b4.Te6edf3BLUETOOTH_CONNECTTb4b4b4)
Tff7b72private Tff7b72fun Td2a8fflowLevelConnectTb4b4b4(Te6edf3autoNowTb4b4b4: Tffa657BooleanTb4b4b4)Tb4b4b4: Te6edf3BluetoothGatt? Tb4b4b4{
Te6edf3currentConnectIsAuto Tff7b72= Te6edf3autoNow
Te6edf3logAssertTb4b4b4(Te6edf3gatt Tff7b72=Tff7b72= Tff7b72nullTb4b4b4)

Tff7b72val Te6edf3g Tff7b72=
Tff7b72if Tb4b4b4(Te6edf3BuildTb4b4b4.Te6edf3VERSIONTb4b4b4.Te6edf3SDK_INT Tff7b72>Tff7b72= Te6edf3BuildTb4b4b4.Te6edf3VERSION_CODESTb4b4b4.Te6edf3MTb4b4b4) Tb4b4b4{
Te6edf3deviceTb4b4b4.Te6edf3connectGattTb4b4b4(Te6edf3contextTb4b4b4, Te6edf3autoNowTb4b4b4, Te6edf3gattCallbackTb4b4b4, Te6edf3BluetoothDeviceTb4b4b4.Te6edf3TRANSPORT_LETb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3deviceTb4b4b4.Te6edf3connectGattTb4b4b4(Te6edf3contextTb4b4b4, Te6edf3autoNowTb4b4b4, Te6edf3gattCallbackTb4b4b4)
Tb4b4b4}

Te6edf3gatt Tff7b72= Te6edf3g
Tff7b72return Te6edf3g
Tb4b4b4}

T8b949e// FIXME, pass in true for autoconnect - so we will autoconnect whenever the radio
T8b949e// comes in range (even if we made this connect call long ago when we got powered on)
T8b949e// see https://stackoverflow.com/questions/40156699/which-correct-flag-of-autoconnect-in-connectgatt-of-ble for
T8b949e// more info.
T8b949e// Otherwise if you pass in false, it will try to connect now and will timeout and fail in 30 seconds.
Tf0883e@RequiresPermissionTb4b4b4(Te6edf3ManifestTb4b4b4.Te6edf3permissionTb4b4b4.Te6edf3BLUETOOTH_CONNECTTb4b4b4)
Tff7b72private Tff7b72fun Td2a8ffqueueConnectTb4b4b4(Te6edf3autoConnectTb4b4b4: Tffa657Boolean Tff7b72= Tff7b72falseTb4b4b4, Te6edf3contTb4b4b4: Te6edf3ContinuationTff7b72<Tffa657UnitTff7b72>Tb4b4b4, Te6edf3timeoutTb4b4b4: Tffa657Long Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Tff7b72thisTb4b4b4.Te6edf3autoConnect Tff7b72= Te6edf3autoConnect

T8b949e// assert(gatt == null) this now might be !null with our new reconnect support
Te6edf3queueWorkTb4b4b4(Ta5d6ff"Ta5d6ffconnectTa5d6ff"Tb4b4b4, Te6edf3contTb4b4b4, Te6edf3timeoutTb4b4b4) Tb4b4b4{
T8b949e// Note: To workaround https://issuetracker.google.com/issues/36995652
T8b949e// Always call BluetoothDevice#connectGatt() with autoConnect=false
T8b949e// (the race condition does not affect that case). If that connection times out
T8b949e// you will get a callback with status=133. Then call BluetoothGatt#connect()
T8b949e// to initiate a background connection.
Tff7b72val Te6edf3g Tff7b72= Te6edf3lowLevelConnectTb4b4b4(Tff7b72falseTb4b4b4)
Te6edf3g Tff7b72!Tff7b72= Tff7b72null
Tb4b4b4}
Tb4b4b4}

T8b949e/**
* start a connection attempt.
*
* Note: if autoConnect is true, the callback you provide will be kept around _even after the connection is
* complete. If we ever lose the connection, this class will immediately requque the attempt (after canceling any
* outstanding queued operations).
*
* So you should expect your callback might be called multiple times, each time to reestablish a new connection.
*/
Tf0883e@RequiresPermissionTb4b4b4(Te6edf3ManifestTb4b4b4.Te6edf3permissionTb4b4b4.Te6edf3BLUETOOTH_CONNECTTb4b4b4)
Tff7b72fun Td2a8ffasyncConnectTb4b4b4(Te6edf3autoConnectTb4b4b4: Tffa657Boolean Tff7b72= Tff7b72falseTb4b4b4, Te6edf3cbTb4b4b4: Tb4b4b4(Te6edf3ResultTff7b72<Tffa657UnitTff7b72>Tb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4, Te6edf3lostConnectCbTb4b4b4: Tb4b4b4(Tb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4) Tb4b4b4{
Te6edf3logAssertTb4b4b4(Te6edf3workQueueTb4b4b4.Te6edf3isEmptyTb4b4b4(Tb4b4b4)Tb4b4b4)

T8b949e// If there's already connection work in progress, clear it before starting new connection
T8b949e// This can happen during reconnection where previous connection work wasn't properly cleared
Tff7b72if Tb4b4b4(Te6edf3currentWork Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3warnTb4b4b4(Ta5d6ff"Ta5d6ffFound existing work during asyncConnect: Tffd700$Te6edf3currentWorkTa5d6ff - clearing itTa5d6ff"Tb4b4b4)
Te6edf3synchronizedTb4b4b4(Te6edf3workQueueTb4b4b4) Tb4b4b4{ Te6edf3stopCurrentWorkTb4b4b4(Tb4b4b4) Tb4b4b4}
Tb4b4b4}

Te6edf3lostConnectCallback Tff7b72= Te6edf3lostConnectCb
Te6edf3connectionCallback Tff7b72=
Tff7b72if Tb4b4b4(Te6edf3autoConnectTb4b4b4) Tb4b4b4{
Te6edf3cb
Tb4b4b4} Tff7b72else Tb4b4b4{
Tff7b72null
Tb4b4b4}
Te6edf3queueConnectTb4b4b4(Te6edf3autoConnectTb4b4b4, Te6edf3CallbackContinuationTb4b4b4(Te6edf3cbTb4b4b4)Tb4b4b4)
Tb4b4b4}

T8b949e// / Restart any previous connect attempts
Tf0883e@RequiresPermissionTb4b4b4(Te6edf3ManifestTb4b4b4.Te6edf3permissionTb4b4b4.Te6edf3BLUETOOTH_CONNECTTb4b4b4)
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffUnusedPrivateMemberTa5d6ff"Tb4b4b4)
Tff7b72private Tff7b72fun Td2a8ffreconnectTb4b4b4(Tb4b4b4) Tb4b4b4{
T8b949e// closeGatt() // Get rid of any old gatt

Te6edf3connectionCallbackTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3cb Tff7b72-Tff7b72> Te6edf3queueConnectTb4b4b4(Tff7b72trueTb4b4b4, Te6edf3CallbackContinuationTb4b4b4(Te6edf3cbTb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}

Tff7b72private Tff7b72fun Td2a8fflostConnectionTb4b4b4(Te6edf3reasonTb4b4b4: Tffa657StringTb4b4b4) Tb4b4b4{
T8b949e/*
Supposedly this reconnect attempt happens automatically
"If the connection was established through an auto connect, Android will
automatically try to reconnect to the remote device when it gets disconnected
until you manually call disconnect() or close(). Once a connection established
through direct connect disconnects, no attempt is made to reconnect to the remote device."
https://stackoverflow.com/questions/37965337/what-exactly-does-androids-bluetooth-autoconnect-parameter-do?rq=1

closeConnection()
*/
Te6edf3failAllWorkTb4b4b4(Te6edf3BLEExceptionTb4b4b4(Te6edf3reasonTb4b4b4)Tb4b4b4)

T8b949e// Cancel any notifications - because when the device comes back it might have forgotten about us
Te6edf3notifyHandlersTb4b4b4.Te6edf3clearTb4b4b4(Tb4b4b4)

Te6edf3lostConnectCallbackTff7b72?.Te6edf3let Tb4b4b4{
Te6edf3debugTb4b4b4(Ta5d6ff"Ta5d6ffcalling lostConnect handlerTa5d6ff"Tb4b4b4)
Tffa657itTb4b4b4.Te6edf3invokeTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4}

T8b949e// / Drop our current connection and then requeue a connect as needed
Tf0883e@RequiresPermissionTb4b4b4(Te6edf3ManifestTb4b4b4.Te6edf3permissionTb4b4b4.Te6edf3BLUETOOTH_CONNECTTb4b4b4)
Tff7b72private Tff7b72fun Td2a8ffdropAndReconnectTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3lostConnectionTb4b4b4(Ta5d6ff"Ta5d6fflost connection, reconnectingTa5d6ff"Tb4b4b4)

T8b949e// Queue a new connection attempt
Tff7b72val Te6edf3cb Tff7b72= Te6edf3connectionCallback
Tff7b72if Tb4b4b4(Te6edf3cb Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3debugTb4b4b4(Ta5d6ff"Ta5d6ffqueuing a reconnection callbackTa5d6ff"Tb4b4b4)
Te6edf3assertTb4b4b4(Te6edf3currentWork Tff7b72=Tff7b72= Tff7b72nullTb4b4b4)

Tff7b72if Tb4b4b4(
Tff7b72!Te6edf3currentConnectIsAuto
Tb4b4b4) Tb4b4b4{ T8b949e// we must have been running during that 1-time manual connect, switch to auto-mode from now on
Te6edf3closeGattTb4b4b4(Tb4b4b4) T8b949e// Close the old non-auto connection
Te6edf3lowLevelConnectTb4b4b4(Tff7b72trueTb4b4b4)
Tb4b4b4}

T8b949e// note - we don't need an init fn (because that would normally redo the connectGatt call - which we don't
T8b949e// need)
Te6edf3queueWorkTb4b4b4(Ta5d6ff"Ta5d6ffreconnectTa5d6ff"Tb4b4b4, Te6edf3CallbackContinuationTb4b4b4(Te6edf3cbTb4b4b4)Tb4b4b4, T79c0ff0Tb4b4b4) Tb4b4b4{ Tff7b72true Tb4b4b4}
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3debugTb4b4b4(Ta5d6ff"Ta5d6ffNo connectionCallback registeredTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4}

Tf0883e@RequiresPermissionTb4b4b4(Te6edf3ManifestTb4b4b4.Te6edf3permissionTb4b4b4.Te6edf3BLUETOOTH_CONNECTTb4b4b4)
Tff7b72fun Td2a8ffconnectTb4b4b4(Te6edf3autoConnectTb4b4b4: Tffa657Boolean Tff7b72= Tff7b72falseTb4b4b4) Tff7b72= Te6edf3makeSyncTff7b72<Tffa657UnitTff7b72> Tb4b4b4{ Te6edf3queueConnectTb4b4b4(Te6edf3autoConnectTb4b4b4, Tffa657itTb4b4b4) Tb4b4b4}

Tf0883e@RequiresPermissionTb4b4b4(Te6edf3ManifestTb4b4b4.Te6edf3permissionTb4b4b4.Te6edf3BLUETOOTH_CONNECTTb4b4b4)
Tff7b72private Tff7b72fun Td2a8ffqueueReadCharacteristicTb4b4b4(
Te6edf3cTb4b4b4: Te6edf3BluetoothGattCharacteristicTb4b4b4,
Te6edf3contTb4b4b4: Te6edf3ContinuationTff7b72<Te6edf3BluetoothGattCharacteristicTff7b72>Tb4b4b4,
Te6edf3timeoutTb4b4b4: Tffa657Long Tff7b72= T79c0ff0Tb4b4b4,
Tb4b4b4) Tff7b72= Te6edf3queueWorkTb4b4b4(Ta5d6ff"Ta5d6ffreadC Tffd700${Te6edf3cTb4b4b4.Te6edf3uuidTffd700}Ta5d6ff"Tb4b4b4, Te6edf3contTb4b4b4, Te6edf3timeoutTb4b4b4) Tb4b4b4{ Te6edf3gattTff7b72!!Tb4b4b4.Te6edf3readCharacteristicTb4b4b4(Te6edf3cTb4b4b4) Tb4b4b4}

Tf0883e@RequiresPermissionTb4b4b4(Te6edf3ManifestTb4b4b4.Te6edf3permissionTb4b4b4.Te6edf3BLUETOOTH_CONNECTTb4b4b4)
Tff7b72fun Td2a8ffasyncReadCharacteristicTb4b4b4(Te6edf3cTb4b4b4: Te6edf3BluetoothGattCharacteristicTb4b4b4, Te6edf3cbTb4b4b4: Tb4b4b4(Te6edf3ResultTff7b72<Te6edf3BluetoothGattCharacteristicTff7b72>Tb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4) Tff7b72=
Te6edf3queueReadCharacteristicTb4b4b4(Te6edf3cTb4b4b4, Te6edf3CallbackContinuationTb4b4b4(Te6edf3cbTb4b4b4)Tb4b4b4)

Tf0883e@RequiresPermissionTb4b4b4(Te6edf3ManifestTb4b4b4.Te6edf3permissionTb4b4b4.Te6edf3BLUETOOTH_CONNECTTb4b4b4)
Tff7b72fun Td2a8ffreadCharacteristicTb4b4b4(Te6edf3cTb4b4b4: Te6edf3BluetoothGattCharacteristicTb4b4b4, Te6edf3timeoutTb4b4b4: Tffa657Long Tff7b72= Te6edf3timeoutMsecTb4b4b4)Tb4b4b4: Te6edf3BluetoothGattCharacteristic Tff7b72=
Te6edf3makeSync Tb4b4b4{
Te6edf3queueReadCharacteristicTb4b4b4(Te6edf3cTb4b4b4, Tffa657itTb4b4b4, Te6edf3timeoutTb4b4b4)
Tb4b4b4}

Tf0883e@RequiresPermissionTb4b4b4(Te6edf3ManifestTb4b4b4.Te6edf3permissionTb4b4b4.Te6edf3BLUETOOTH_CONNECTTb4b4b4)
Tff7b72private Tff7b72fun Td2a8ffqueueDiscoverServicesTb4b4b4(Te6edf3contTb4b4b4: Te6edf3ContinuationTff7b72<Tffa657UnitTff7b72>Tb4b4b4, Te6edf3timeoutTb4b4b4: Tffa657Long Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Te6edf3queueWorkTb4b4b4(Ta5d6ff"Ta5d6ffdiscoverTa5d6ff"Tb4b4b4, Te6edf3contTb4b4b4, Te6edf3timeoutTb4b4b4) Tb4b4b4{
Te6edf3gattTff7b72?.Te6edf3discoverServicesTb4b4b4(Tb4b4b4)
Tff7b72?: Tff7b72false T8b949e// throw BLEException("GATT is null") - if we return false here it is probably because the
T8b949e// device is being torn down
Tb4b4b4}
Tb4b4b4}

Tf0883e@RequiresPermissionTb4b4b4(Te6edf3ManifestTb4b4b4.Te6edf3permissionTb4b4b4.Te6edf3BLUETOOTH_CONNECTTb4b4b4)
Tff7b72fun Td2a8ffasyncDiscoverServicesTb4b4b4(Te6edf3cbTb4b4b4: Tb4b4b4(Te6edf3ResultTff7b72<Tffa657UnitTff7b72>Tb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4) Tb4b4b4{
Te6edf3queueDiscoverServicesTb4b4b4(Te6edf3CallbackContinuationTb4b4b4(Te6edf3cbTb4b4b4)Tb4b4b4)
Tb4b4b4}

Tf0883e@RequiresPermissionTb4b4b4(Te6edf3ManifestTb4b4b4.Te6edf3permissionTb4b4b4.Te6edf3BLUETOOTH_CONNECTTb4b4b4)
Tff7b72fun Td2a8ffdiscoverServicesTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3makeSyncTff7b72<Tffa657UnitTff7b72> Tb4b4b4{ Te6edf3queueDiscoverServicesTb4b4b4(Tffa657itTb4b4b4) Tb4b4b4}

T8b949e/** On some phones we receive bogus mtu gatt callbacks, we need to ignore them if we weren't setting the mtu */
Tff7b72private Tff7b72var Te6edf3isSettingMtu Tff7b72= Tff7b72false

T8b949e/**
* mtu operations seem to hang sometimes. To cope with this we have a 5 second timeout before throwing an exception
* and cancelling the work
*/
Tf0883e@RequiresPermissionTb4b4b4(Te6edf3ManifestTb4b4b4.Te6edf3permissionTb4b4b4.Te6edf3BLUETOOTH_CONNECTTb4b4b4)
Tff7b72private Tff7b72fun Td2a8ffqueueRequestMtuTb4b4b4(Te6edf3lenTb4b4b4: Tffa657IntTb4b4b4, Te6edf3contTb4b4b4: Te6edf3ContinuationTff7b72<Tffa657UnitTff7b72>Tb4b4b4) Tff7b72= Te6edf3queueWorkTb4b4b4(Ta5d6ff"Ta5d6ffreqMtuTa5d6ff"Tb4b4b4, Te6edf3contTb4b4b4, T79c0ff1T79c0ff0 Tff7b72* T79c0ff1T79c0ff0T79c0ff0T79c0ff0Tb4b4b4) Tb4b4b4{
Te6edf3isSettingMtu Tff7b72= Tff7b72true
Te6edf3gattTff7b72?.Te6edf3requestMtuTb4b4b4(Te6edf3lenTb4b4b4) Tff7b72?: Tff7b72false
Tb4b4b4}

Tf0883e@RequiresPermissionTb4b4b4(Te6edf3ManifestTb4b4b4.Te6edf3permissionTb4b4b4.Te6edf3BLUETOOTH_CONNECTTb4b4b4)
Tff7b72fun Td2a8ffasyncRequestMtuTb4b4b4(Te6edf3lenTb4b4b4: Tffa657IntTb4b4b4, Te6edf3cbTb4b4b4: Tb4b4b4(Te6edf3ResultTff7b72<Tffa657UnitTff7b72>Tb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4) Tb4b4b4{
Te6edf3queueRequestMtuTb4b4b4(Te6edf3lenTb4b4b4, Te6edf3CallbackContinuationTb4b4b4(Te6edf3cbTb4b4b4)Tb4b4b4)
Tb4b4b4}

Tf0883e@RequiresPermissionTb4b4b4(Te6edf3ManifestTb4b4b4.Te6edf3permissionTb4b4b4.Te6edf3BLUETOOTH_CONNECTTb4b4b4)
Tff7b72fun Td2a8ffrequestMtuTb4b4b4(Te6edf3lenTb4b4b4: Tffa657IntTb4b4b4)Tb4b4b4: Tffa657Unit Tff7b72= Te6edf3makeSync Tb4b4b4{ Te6edf3queueRequestMtuTb4b4b4(Te6edf3lenTb4b4b4, Tffa657itTb4b4b4) Tb4b4b4}

Tff7b72private Tff7b72var Te6edf3currentReliableWriteTb4b4b4: Te6edf3ByteArray? Tff7b72= Tff7b72null

Tf0883e@RequiresPermissionTb4b4b4(Te6edf3ManifestTb4b4b4.Te6edf3permissionTb4b4b4.Te6edf3BLUETOOTH_CONNECTTb4b4b4)
Tff7b72private Tff7b72fun Td2a8ffqueueWriteCharacteristicTb4b4b4(
Te6edf3cTb4b4b4: Te6edf3BluetoothGattCharacteristicTb4b4b4,
Te6edf3vTb4b4b4: Te6edf3ByteArrayTb4b4b4,
Te6edf3contTb4b4b4: Te6edf3ContinuationTff7b72<Te6edf3BluetoothGattCharacteristicTff7b72>Tb4b4b4,
Te6edf3timeoutTb4b4b4: Tffa657Long Tff7b72= T79c0ff0Tb4b4b4,
Tb4b4b4) Tff7b72= Te6edf3queueWorkTb4b4b4(Ta5d6ff"Ta5d6ffwriteC Tffd700${Te6edf3cTb4b4b4.Te6edf3uuidTffd700}Ta5d6ff"Tb4b4b4, Te6edf3contTb4b4b4, Te6edf3timeoutTb4b4b4) Tb4b4b4{
Te6edf3currentReliableWrite Tff7b72= Tff7b72null
Te6edf3cTb4b4b4.Te6edf3value Tff7b72= Te6edf3v
Te6edf3gattTff7b72?.Te6edf3writeCharacteristicTb4b4b4(Te6edf3cTb4b4b4) Tff7b72?: Tff7b72false
Tb4b4b4}

Tf0883e@RequiresPermissionTb4b4b4(Te6edf3ManifestTb4b4b4.Te6edf3permissionTb4b4b4.Te6edf3BLUETOOTH_CONNECTTb4b4b4)
Tff7b72fun Td2a8ffasyncWriteCharacteristicTb4b4b4(
Te6edf3cTb4b4b4: Te6edf3BluetoothGattCharacteristicTb4b4b4,
Te6edf3vTb4b4b4: Te6edf3ByteArrayTb4b4b4,
Te6edf3cbTb4b4b4: Tb4b4b4(Te6edf3ResultTff7b72<Te6edf3BluetoothGattCharacteristicTff7b72>Tb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4,
Tb4b4b4) Tff7b72= Te6edf3queueWriteCharacteristicTb4b4b4(Te6edf3cTb4b4b4, Te6edf3vTb4b4b4, Te6edf3CallbackContinuationTb4b4b4(Te6edf3cbTb4b4b4)Tb4b4b4)

Tf0883e@RequiresPermissionTb4b4b4(Te6edf3ManifestTb4b4b4.Te6edf3permissionTb4b4b4.Te6edf3BLUETOOTH_CONNECTTb4b4b4)
Tff7b72fun Td2a8ffwriteCharacteristicTb4b4b4(
Te6edf3cTb4b4b4: Te6edf3BluetoothGattCharacteristicTb4b4b4,
Te6edf3vTb4b4b4: Te6edf3ByteArrayTb4b4b4,
Te6edf3timeoutTb4b4b4: Tffa657Long Tff7b72= Te6edf3timeoutMsecTb4b4b4,
Tb4b4b4)Tb4b4b4: Te6edf3BluetoothGattCharacteristic Tff7b72= Te6edf3makeSync Tb4b4b4{ Te6edf3queueWriteCharacteristicTb4b4b4(Te6edf3cTb4b4b4, Te6edf3vTb4b4b4, Tffa657itTb4b4b4, Te6edf3timeoutTb4b4b4) Tb4b4b4}

T8b949e/**
* Like write, but we use the extra reliable flow documented here:
* https://stackoverflow.com/questions/24485536/what-is-reliable-write-in-ble
*/
Tf0883e@RequiresPermissionTb4b4b4(Te6edf3ManifestTb4b4b4.Te6edf3permissionTb4b4b4.Te6edf3BLUETOOTH_CONNECTTb4b4b4)
Tff7b72private Tff7b72fun Td2a8ffqueueWriteReliableTb4b4b4(Te6edf3cTb4b4b4: Te6edf3BluetoothGattCharacteristicTb4b4b4, Te6edf3contTb4b4b4: Te6edf3ContinuationTff7b72<Tffa657UnitTff7b72>Tb4b4b4, Te6edf3timeoutTb4b4b4: Tffa657Long Tff7b72= T79c0ff0Tb4b4b4) Tff7b72=
Te6edf3queueWorkTb4b4b4(Ta5d6ff"Ta5d6ffrwriteC Tffd700${Te6edf3cTb4b4b4.Te6edf3uuidTffd700}Ta5d6ff"Tb4b4b4, Te6edf3contTb4b4b4, Te6edf3timeoutTb4b4b4) Tb4b4b4{
Te6edf3logAssertTb4b4b4(Te6edf3gattTff7b72!!Tb4b4b4.Te6edf3beginReliableWriteTb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3currentReliableWrite Tff7b72= Te6edf3cTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3cloneTb4b4b4(Tb4b4b4)
Te6edf3gattTff7b72?.Te6edf3writeCharacteristicTb4b4b4(Te6edf3cTb4b4b4) Tff7b72?: Tff7b72false
Tb4b4b4}

Tf0883e@RequiresPermissionTb4b4b4(Te6edf3ManifestTb4b4b4.Te6edf3permissionTb4b4b4.Te6edf3BLUETOOTH_CONNECTTb4b4b4)
Tff7b72fun Td2a8ffasyncWriteReliableTb4b4b4(Te6edf3cTb4b4b4: Te6edf3BluetoothGattCharacteristicTb4b4b4, Te6edf3cbTb4b4b4: Tb4b4b4(Te6edf3ResultTff7b72<Tffa657UnitTff7b72>Tb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4) Tff7b72=
Te6edf3queueWriteReliableTb4b4b4(Te6edf3cTb4b4b4, Te6edf3CallbackContinuationTb4b4b4(Te6edf3cbTb4b4b4)Tb4b4b4)

Tf0883e@RequiresPermissionTb4b4b4(Te6edf3ManifestTb4b4b4.Te6edf3permissionTb4b4b4.Te6edf3BLUETOOTH_CONNECTTb4b4b4)
Tff7b72fun Td2a8ffwriteReliableTb4b4b4(Te6edf3cTb4b4b4: Te6edf3BluetoothGattCharacteristicTb4b4b4)Tb4b4b4: Tffa657Unit Tff7b72= Te6edf3makeSync Tb4b4b4{ Te6edf3queueWriteReliableTb4b4b4(Te6edf3cTb4b4b4, Tffa657itTb4b4b4) Tb4b4b4}

Tf0883e@RequiresPermissionTb4b4b4(Te6edf3ManifestTb4b4b4.Te6edf3permissionTb4b4b4.Te6edf3BLUETOOTH_CONNECTTb4b4b4)
Tff7b72private Tff7b72fun Td2a8ffqueueWriteDescriptorTb4b4b4(
Te6edf3cTb4b4b4: Te6edf3BluetoothGattDescriptorTb4b4b4,
Te6edf3contTb4b4b4: Te6edf3ContinuationTff7b72<Te6edf3BluetoothGattDescriptorTff7b72>Tb4b4b4,
Te6edf3timeoutTb4b4b4: Tffa657Long Tff7b72= T79c0ff0Tb4b4b4,
Tb4b4b4) Tff7b72= Te6edf3queueWorkTb4b4b4(Ta5d6ff"Ta5d6ffwriteDTa5d6ff"Tb4b4b4, Te6edf3contTb4b4b4, Te6edf3timeoutTb4b4b4) Tb4b4b4{ Te6edf3gattTff7b72?.Te6edf3writeDescriptorTb4b4b4(Te6edf3cTb4b4b4) Tff7b72?: Tff7b72false Tb4b4b4}

Tf0883e@RequiresPermissionTb4b4b4(Te6edf3ManifestTb4b4b4.Te6edf3permissionTb4b4b4.Te6edf3BLUETOOTH_CONNECTTb4b4b4)
Tff7b72fun Td2a8ffasyncWriteDescriptorTb4b4b4(Te6edf3cTb4b4b4: Te6edf3BluetoothGattDescriptorTb4b4b4, Te6edf3cbTb4b4b4: Tb4b4b4(Te6edf3ResultTff7b72<Te6edf3BluetoothGattDescriptorTff7b72>Tb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4) Tff7b72=
Te6edf3queueWriteDescriptorTb4b4b4(Te6edf3cTb4b4b4, Te6edf3CallbackContinuationTb4b4b4(Te6edf3cbTb4b4b4)Tb4b4b4)

T8b949e/**
* Some old androids have a bug where calling disconnect doesn't guarantee that the onConnectionStateChange callback
* gets called but the only safe way to call gatt.close is from that callback. So we set a flag once we start
* closing and then poll until we see the callback has set gatt to null (indicating the CALLBACK has close the
* gatt). If the timeout expires we assume the bug has occurred, and we manually close the gatt here.
*
* Log of typical failure 06-29 08:47:15.035 29788-30155/com.geeksville.mesh D/BluetoothGatt: cancelOpen() - device:
* 24:62:AB:F8:40:9A 06-29 08:47:15.036 29788-30155/com.geeksville.mesh D/BluetoothGatt: close() 06-29 08:47:15.037
* 29788-30155/com.geeksville.mesh D/BluetoothGatt: unregisterApp() - mClientIf=5 06-29 08:47:15.037
* 29788-29813/com.geeksville.mesh D/BluetoothGatt: onClientConnectionState() - status=0 clientIf=5
* device=24:62:AB:F8:40:9A 06-29 08:47:15.037 29788-29813/com.geeksville.mesh W/BluetoothGatt: Unhandled exception
* in callback java.lang.NullPointerException: Attempt to invoke virtual method 'void
* android.bluetooth.BluetoothGattCallback.onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)' on a
* null object reference at android.bluetooth.BluetoothGatt$1.onClientConnectionState(BluetoothGatt.java:182) at
* android.bluetooth.IBluetoothGattCallback$Stub.onTransact(IBluetoothGattCallback.java:70) at
* android.os.Binder.execTransact(Binder.java:446)
*
* per https://github.com/don/cordova-plugin-ble-central/issues/473#issuecomment-367687575
*/
Tf0883e@Volatile Tff7b72private Tff7b72var Te6edf3isClosing Tff7b72= Tff7b72false

T8b949e/** Close just the GATT device but keep our pending callbacks active */
Tf0883e@RequiresPermissionTb4b4b4(Te6edf3ManifestTb4b4b4.Te6edf3permissionTb4b4b4.Te6edf3BLUETOOTH_CONNECTTb4b4b4)
Tff7b72fun Td2a8ffcloseGattTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3gattTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3g Tff7b72-Tff7b72>
Te6edf3infoTb4b4b4(Ta5d6ff"Ta5d6ffClosing our GATT connectionTa5d6ff"Tb4b4b4)
Te6edf3isClosing Tff7b72= Tff7b72true
Tff7b72try Tb4b4b4{
Te6edf3gTb4b4b4.Te6edf3disconnectTb4b4b4(Tb4b4b4)

T8b949e// Wait for our callback to run and handle the disconnect, with a timeout.
Te6edf3runBlocking Tb4b4b4{
Te6edf3withTimeoutOrNullTb4b4b4(T79c0ff1T79c0ff0T79c0ff0T79c0ff0Tb4b4b4) Tb4b4b4{
Tff7b72while Tb4b4b4(Te6edf3gatt Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3delayTb4b4b4(T79c0ff1T79c0ff0T79c0ff0Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

Te6edf3gattTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3g2 Tff7b72-Tff7b72>
Te6edf3warnTb4b4b4(Ta5d6ff"Ta5d6ffAndroid onConnectionStateChange did not run, manually closingTa5d6ff"Tb4b4b4)
Te6edf3gatt Tff7b72= Tff7b72null T8b949e// clear gatt before calling close, because close might throw dead object exception
Te6edf3g2Tb4b4b4.Te6edf3closeTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3exTb4b4b4: Te6edf3NullPointerExceptionTb4b4b4) Tb4b4b4{
T8b949e// Attempt to invoke virtual method 'com.android.bluetooth.gatt.AdvertiseClient
T8b949e// com.android.bluetooth.gatt.AdvertiseManager.getAdvertiseClient(int)' on a null object reference
T8b949e// com.geeksville.mesh.service.SafeBluetooth.closeGatt
Te6edf3warnTb4b4b4(Ta5d6ff"Ta5d6ffIgnoring NPE in close - probably buggy Samsung BLETa5d6ff"Tb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3exTb4b4b4: Te6edf3DeadObjectExceptionTb4b4b4) Tb4b4b4{
Te6edf3warnTb4b4b4(Ta5d6ff"Ta5d6ffIgnoring dead object exception, probably bluetooth was just disabledTa5d6ff"Tb4b4b4)
Tb4b4b4} Tff7b72finally Tb4b4b4{
Te6edf3isClosing Tff7b72= Tff7b72false
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

T8b949e/**
* Close down any existing connection, any existing calls (including async connects will be cancelled and you'll
* need to recall connect to use this againt
*/
Tf0883e@RequiresPermissionTb4b4b4(Te6edf3ManifestTb4b4b4.Te6edf3permissionTb4b4b4.Te6edf3BLUETOOTH_CONNECTTb4b4b4)
Tff7b72fun Td2a8ffcloseConnectionTb4b4b4(Tb4b4b4) Tb4b4b4{
T8b949e// Set these to null _before_ calling gatt.disconnect(), because we don't want the old lostConnectCallback to
T8b949e// get called
Te6edf3lostConnectCallback Tff7b72= Tff7b72null
Te6edf3connectionCallback Tff7b72= Tff7b72null

T8b949e// Cancel any notifications - because when the device comes back it might have forgotten about us
Te6edf3notifyHandlersTb4b4b4.Te6edf3clearTb4b4b4(Tb4b4b4)

Te6edf3closeGattTb4b4b4(Tb4b4b4)

Te6edf3failAllWorkTb4b4b4(Te6edf3BLEConnectionClosingTb4b4b4(Tb4b4b4)Tb4b4b4)
Tb4b4b4}

Tf0883e@RequiresPermissionTb4b4b4(Te6edf3ManifestTb4b4b4.Te6edf3permissionTb4b4b4.Te6edf3BLUETOOTH_CONNECTTb4b4b4)
T8b949e/** Close and destroy this SafeBluetooth instance. You'll need to make a new instance before using it again */
Tff7b72override Tff7b72fun Td2a8ffcloseTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3closeConnectionTb4b4b4(Tb4b4b4)

T8b949e// context.unregisterReceiver(btStateReceiver)
Tb4b4b4}

T8b949e// / asyncronously turn notification on/off for a characteristic
Tf0883e@RequiresPermissionTb4b4b4(Te6edf3ManifestTb4b4b4.Te6edf3permissionTb4b4b4.Te6edf3BLUETOOTH_CONNECTTb4b4b4)
Tff7b72fun Td2a8ffsetNotifyTb4b4b4(Te6edf3cTb4b4b4: Te6edf3BluetoothGattCharacteristicTb4b4b4, Te6edf3enableTb4b4b4: Tffa657BooleanTb4b4b4, Te6edf3onChangedTb4b4b4: Tb4b4b4(Te6edf3BluetoothGattCharacteristicTb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4) Tb4b4b4{
Te6edf3debugTb4b4b4(Ta5d6ff"Ta5d6ffstarting setNotify(Tffd700${Te6edf3cTb4b4b4.Te6edf3uuidTffd700}Ta5d6ff, Tffd700$Te6edf3enableTa5d6ff)Ta5d6ff"Tb4b4b4)
Te6edf3notifyHandlersTff7b72[Te6edf3cTb4b4b4.Te6edf3uuidTff7b72] Tff7b72= Te6edf3onChanged
T8b949e// c.writeType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
Te6edf3gattTff7b72!!Tb4b4b4.Te6edf3setCharacteristicNotificationTb4b4b4(Te6edf3cTb4b4b4, Te6edf3enableTb4b4b4)

T8b949e// per https://stackoverflow.com/questions/27068673/subscribe-to-a-ble-gatt-notification-android
Tff7b72val Te6edf3descriptorTb4b4b4: Te6edf3BluetoothGattDescriptor Tff7b72=
Te6edf3cTb4b4b4.Te6edf3getDescriptorTb4b4b4(Te6edf3configurationDescriptorUUIDTb4b4b4)
Tff7b72?: Tff7b72throw Te6edf3BLEExceptionTb4b4b4(
Ta5d6ff"Ta5d6ffNotify descriptor not found for Tffd700${Te6edf3cTb4b4b4.Te6edf3uuidTffd700}Ta5d6ff"Tb4b4b4,
Tb4b4b4) T8b949e// This can happen on buggy BLE implementations
Te6edf3descriptorTb4b4b4.Te6edf3value Tff7b72=
Tff7b72if Tb4b4b4(Te6edf3enableTb4b4b4) Tb4b4b4{
Te6edf3BluetoothGattDescriptorTb4b4b4.Te6edf3ENABLE_NOTIFICATION_VALUE
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3BluetoothGattDescriptorTb4b4b4.Te6edf3DISABLE_NOTIFICATION_VALUE
Tb4b4b4}
Te6edf3asyncWriteDescriptorTb4b4b4(Te6edf3descriptorTb4b4b4) Tb4b4b4{ Te6edf3debugTb4b4b4(Ta5d6ff"Ta5d6ffNotify enable=Tffd700$Te6edf3enableTa5d6ff completedTa5d6ff"Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

Served by rngit 1.5.0 - Generated in 0.2s